package com.magima.core.basic;

import java.lang.ref.WeakReference;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;

import android.os.Handler;
import android.os.Looper;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import com.magima.core.connect.listener.ServerDataListener;

public class Model<T> {
	public interface UrlGenerator<T> {
		public String getUrl(Model<T> model);
	}
	
	private T model;
	private T previousModel;
	private Class<?> typeOfT;
	private WeakReference<Collection<T>> collection;
	private Storage<T> storage = null;
	private UrlGenerator<T> urlGenerator = null;
	private static Handler mHandler = new Handler();
	private boolean mFetched;
	private boolean mFetching;
	private Gson gson;
	
	private Lock mLock = new ReentrantLock(true);
	
	public Model(T model, Class<?> typeOfT) {
		this.model = model;
		this.typeOfT = typeOfT;
		mFetched = false;
		mFetching = false;
	}

	public interface Listener<T> {
		void onModelChanged(T model);

		void onModelDestroy(T model);
	}

	private List<WeakReference<Listener<T>>> listeners = new ArrayList<WeakReference<Listener<T>>>();
	private Lock mListenerLock = new ReentrantLock(true);
	
	public void addListener(Listener<T> listener) {
		mListenerLock.lock();
		try {
			listeners.add(new WeakReference<Model.Listener<T>>(listener));
		} finally {
			mListenerLock.unlock();
		}
	}

	public void removeListener(Listener<T> listener) {
		mListenerLock.lock();
		try {
			List<WeakReference<Listener<T>>> delList = new ArrayList<WeakReference<Listener<T>>>();
			for (Iterator<WeakReference<Listener<T>>> iterator = listeners
					.iterator(); iterator.hasNext();) {
				WeakReference<Listener<T>> weakRef = iterator.next();
				if (weakRef.get() == listener || weakRef.get() == null) {
					delList.add(weakRef);
				}
			}
			listeners.removeAll(delList);
		} finally {
			mListenerLock.unlock();
		}
	}

	public void setStorage(Storage<T> storage) {
		this.storage = storage;
	}

	public Storage<T> getStorage() {
		return this.storage;
	}

	public void setUrlGenerator(UrlGenerator<T> generator) {
		this.urlGenerator = generator;
	}

	public UrlGenerator<T> getUrlGenerator() {
		return this.urlGenerator;
	}

	public T getModel() {
		return model;
	}
	
	public T getPrevModel() {
		return previousModel;
	}

	public void update(T model, final StorageDataListener<T> listener,
			final boolean silent, HashMap<String, Object> options) {
		Storage<T> s = storage == null ? ( ( this.collection == null || this.collection.get() == null) ? null
				: this.collection.get().getStorage()) : storage;
		if (s != null) { 
			s.update(this, model, new ServerDataListener<T>() {
				@Override
				public void onSuccess(T data, String resp) {
					update(data, silent);
					if ( listener != null )
						listener.onSuccess(Model.this);
				}

				@Override
				public void onError() {
					if ( listener != null )
						listener.onError(Model.this);
				}
			}, options);
		} else
			update(this.model, silent);
	}

	public final void update() {
		update(model, false);
	}

	public final void update(T model) {
		update(model, false);
	}

	private void notifyUpdate(){
		mListenerLock.lock();
		try {
			List<WeakReference<Listener<T>>> delList = new ArrayList<WeakReference<Listener<T>>>();
			for (Iterator<WeakReference<Listener<T>>> iterator = listeners
					.iterator(); iterator.hasNext();) {
				WeakReference<Listener<T>> weakRef = iterator.next();
				if (weakRef.get() == null) {
					delList.add(weakRef);
				} else {
					weakRef.get().onModelChanged(model);
				}
			}
			listeners.removeAll(delList);
			if (this.collection != null && this.collection.get() != null) {
				this.collection.get().notifyModelChange(this);
			}
		} finally {
			mListenerLock.unlock();
		}
	}
	
	private Gson getGSon(){
		if ( gson == null )
			gson = new GsonBuilder().create();
		return gson;
	}
	
	public void update(T model, boolean silent) {
		update(model, silent, false);
	}
	
	@SuppressWarnings("unchecked")
	public void update(T model, boolean silent, boolean savePrevious) {
		if ( savePrevious ) {
			previousModel = (T) getGSon().fromJson(getGSon().toJson(this.model), typeOfT);
		}
		this.model = model;
		if (!silent) {
			if ( Looper.myLooper() == Looper.getMainLooper() ) {
				notifyUpdate();
			} else {
				mHandler.post(new Runnable(){
					@Override
					public void run() {
						notifyUpdate();
					}				
				});
			}
		}
	}

	public final void update(final StorageDataListener<T> listener) {
		update(this.model, listener, false, null);
	}

	public final void update(T model, StorageDataListener<T> listener) {
		this.model = model;
		update(model, listener, false, null);
	}

	public void destroy(final StorageDataListener<T> listener,
			final boolean silent, HashMap<String, Object> options) {
		Collection<T> c = this.getCollection();
		Storage<T> s = storage == null ? (c == null ? null : c.getStorage())
				: storage;
		if (s != null) {
			s.delete(c, this, new ServerDataListener<T>() {
				@Override
				public void onSuccess(T data, String resp) {
					if ( listener != null )
						listener.onSuccess(Model.this);
					destroy(silent);
				}

				@Override
				public void onError() {
					if ( listener != null)
						listener.onError(Model.this);
				}
			}, options);
		} else
			destroy(silent);
	}

	private void notifyDestroy(){
		mListenerLock.lock();
		try {
			List<WeakReference<Listener<T>>> delList = new ArrayList<WeakReference<Listener<T>>>();
			for (Iterator<WeakReference<Listener<T>>> iterator = listeners
					.iterator(); iterator.hasNext();) {
				WeakReference<Listener<T>> weakRef = iterator.next();
				if (weakRef.get() == null) {
					delList.add(weakRef);
				} else {
					weakRef.get().onModelDestroy(model);
				}
			}
			listeners.removeAll(delList);
		} finally {
			mListenerLock.unlock();
		}
	}
	
	public void destroy(boolean silent) {
		if (this.getCollection() != null) {
			this.getCollection().remove(this, silent);
		}
		if (!silent) {
			if ( Looper.myLooper() == Looper.getMainLooper() ) {
				notifyDestroy();
//				this.model = null;
			} else {
				mHandler.post(new Runnable(){
					@Override
					public void run() {
						notifyDestroy();
//						model = null;
					}				
				});
			}
		}
	}

	public final void destroy() {
		destroy(false);
	}

	public final void destroy(StorageDataListener<T> listener) {
		destroy(listener, false, null);
	}
	
	public final void moveTo(final StorageDataListener<T> listener,
			final Collection<T> collection, final boolean silent,
			HashMap<String, Object> fromOptions, final HashMap<String, Object> toOptions){
		final Collection<T> c = this.getCollection();
		Storage<T> s = storage == null ? (c == null ? null : c.getStorage())
				: storage;
		if (s != null) {
			s.delete(c, this, new ServerDataListener<T>() {
				@Override
				public void onSuccess(T data, String resp) {
					c.remove(Model.this, silent);
					
					Storage<T> s2 = storage == null ? (collection.getStorage())
							: storage;
					if (s2 != null) {
						s2.create(collection, model, new ServerDataListener<T>() {
							@Override
							public void onSuccess(T data, String resp) {
								collection.add(Model.this, silent);
								if (listener != null)
									listener.onSuccess(Model.this);
							}

							@Override
							public void onError() {
								if (listener != null)
									listener.onError(null);
							}
						}, toOptions);
					} else {
						collection.add(Model.this, silent);
						if (listener != null)
							listener.onSuccess(Model.this);
					}
				}

				@Override
				public void onError() {
					if ( listener != null)
						listener.onError(Model.this);
				}
			}, fromOptions);			
		} else {
			if(c != null)
				c.remove(Model.this, silent);
			
			Storage<T> s2 = storage == null ? (collection.getStorage())
					: storage;
			if (s2 != null) {
				s2.create(collection, model, new ServerDataListener<T>() {
					@Override
					public void onSuccess(T data, String resp) {
						collection.add(Model.this, silent);
						if (listener != null)
							listener.onSuccess(Model.this);
					}

					@Override
					public void onError() {
						if (listener != null)
							listener.onError(null);
					}
				}, toOptions);
			} else {
				collection.add(Model.this, silent);
				if (listener != null)
					listener.onSuccess(Model.this);
			}
		}
	}
	
	public boolean isFetched() {
		return mFetched;
	}
	
	public boolean isFetching(){
		return mFetching;
	}
	
	public void fetch(final StorageDataListener<T> listener, HashMap<String, Object> options) {
		fetch(listener, false, options);
	}

	public void fetch(final StorageDataListener<T> listener) {
		fetch(listener, false, null);
	}

	public void fetch(final StorageDataListener<T> listener,
			final boolean silent,
			HashMap<String, Object> options) {
		Storage<T> s = storage == null ? ((this.collection != null && this.collection.get() == null) ? null
				: this.collection.get().getStorage()) : storage;
		if (s != null) {
			mFetching = true;
			s.read(this, new ServerDataListener<T>() {
				@Override
				public void onSuccess(T data, String resp) {
					mFetched = true;
					mFetching = false;
					update(data, silent);
					if ( listener != null )
						listener.onSuccess(Model.this);
				}

				@Override
				public void onError() {
					if ( listener != null )
						listener.onError(Model.this);
				}
			}, options);
		}
	}

	public Collection<T> getCollection() {
		if (this.collection == null)
			return null;
		return this.collection.get();
	}

	public void setCollection(Collection<T> c) {
		if (c == null)
			this.collection = null;
		else {
			this.collection = new WeakReference<Collection<T>>(c);
		}
	}
	
	public String toJSON(){
		return getGSon().toJson(this.model);
	}

	public void lock(){
		mLock.lock();
	}
	
	public void unlock(){
		mLock.unlock();
	}
}
